Tutorial Setup Next.js + Prisma v6 + MySQL (Database Existing)

Dokumentasi ini merangkum proses setup dari nol: membuat project Next.js, menghubungkan ke database MySQL yang sudah ada menggunakan Prisma v6, sampai menampilkan data lewat route.


0. Kesimpulan Penting Sebelum Mulai

Sempat dicoba pakai Prisma v7, tapi ada beberapa perubahan besar di versi itu yang bikin proses jadi rumit untuk pemula:

Isu di Prisma v7 Penyebab
PrismaClient tidak ter-export dari @prisma/client v7 mewajibkan custom output path, client tidak lagi otomatis masuk ke node_modules/@prisma/client
new PrismaClient() error "Expected 1 arguments, got 0" v7 menghapus query engine internal, wajib pakai driver adapter (@prisma/adapter-mariadb, dll)
datasourceUrl dianggap unknown property Opsi lama untuk override URL tidak berlaku lagi di v7

Keputusan: pakai Prisma v6 karena lebih stabil, masih pakai query engine bawaan (tidak perlu adapter), dan proses generate client jauh lebih simpel — cocok untuk belajar dari nol.


1. Buat Project Next.js

npx create-next-app@latest nama-project

Pilihan yang direkomendasikan:

  • TypeScript → Yes
  • App Router → Yes
  • src/ directory → catat pilihanmu, ini menentukan lokasi file nanti (contoh di tutorial ini tidak pakai src/, langsung app/ di root)
cd nama-project
ls

2. Install Prisma v6 (Eksplisit — Jangan @latest)

Supaya tidak ke-pull v7 secara tidak sengaja:

npm install prisma@6 --save-dev
npm install @prisma/client@6

Verifikasi versi:

npx prisma -v

Pastikan hasilnya 6.x.x.


3. Inisialisasi Prisma dengan MySQL

npx prisma init --datasource-provider mysql

Perintah ini otomatis membuat:

  • prisma/schema.prisma dengan provider = "mysql"
  • prisma.config.ts (fitur konfigurasi baru di Prisma)
  • .env dengan template DATABASE_URL

Tentang prisma.config.ts — Prisma versi terbaru memindahkan sebagian konfigurasi (URL, path migrations, engine) ke file ini. Contoh isi yang valid:

import "dotenv/config";
import { defineConfig, env } from "prisma/config";

export default defineConfig({
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
  },
  engine: "classic",
  datasource: {
    url: env("DATABASE_URL"),
  },
});

Selama ada import "dotenv/config" dan env("DATABASE_URL"), isi .env tetap terbaca normal. Pesan "Prisma config detected, skipping environment variable loading" saat menjalankan CLI bukan error.


4. Isi Connection String di .env

DATABASE_URL="mysql://user:password@localhost:3306/nama_database"

Ganti user, password, dan nama_database sesuai kredensial MySQL kamu.


5. Tarik Struktur Tabel yang Sudah Ada (Introspeksi)

Karena database sudah berisi tabel (misalnya users, role, rolecat, appmenunew, dll):

npx prisma db pull

Cek hasilnya:

cat prisma/schema.prisma

Prisma otomatis membaca seluruh tabel & relasi (foreign key) dari database dan menuliskannya sebagai model.


6. Generate Prisma Client

npx prisma generate

Penting: cek output-nya untuk tahu ke mana client di-generate, misalnya:

✔ Generated Prisma Client (6.x.x) to ./app/generated/prisma

Kalau generator client di schema kamu punya baris output custom, maka import Prisma Client tidak boleh dari @prisma/client, tapi dari path itu langsung.


7. Cek Alias @/ di tsconfig.json

cat tsconfig.json | grep -A 3 paths

Untuk project tanpa folder src/, pastikan:

"paths": {
  "@/*": ["./*"]
}

8. Buat File Singleton Prisma Client

mkdir -p lib
// lib/prisma.ts
import { PrismaClient } from '../app/generated/prisma'
// sesuaikan path ini dengan lokasi output di schema.prisma kamu

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined
}

export const prisma = globalForPrisma.prisma ?? new PrismaClient()

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma

Kenapa pola singleton ini? Next.js dev mode melakukan hot-reload berkali-kali. Tanpa pola globalForPrisma, tiap reload akan membuat koneksi Prisma Client baru dan lama-lama menghabiskan koneksi database.


9. Buat Route untuk Menampilkan Data

Aturan Routing Next.js App Router (Penting!):

Struktur Hasil
app/roles/page.tsx✅ jadi route /roles
app/roles.tsx❌ tidak dikenali sebagai route sama sekali
app/users/page.tsx✅ jadi route /users

Route wajib berupa folder dengan file bernama persis page.tsx di dalamnya — bukan file lepas.

Contoh Route Sederhana — /users

mkdir -p app/users
// app/users/page.tsx
import { prisma } from '@/lib/prisma'

export default async function UsersPage() {
  const users = await prisma.users.findMany({
    select: {
      id: true,
      name: true,
      email: true,
      level: true,
    },
    orderBy: { id: 'desc' },
  })

  if (users.length === 0) {
    return <p className="p-6 text-gray-500">Belum ada data user.</p>
  }

  return (
    <div className="p-6">
      <h1 className="text-2xl font-bold mb-4">Daftar User</h1>
      <table className="w-full border-collapse">
        <thead>
          <tr className="border-b">
            <th className="text-left p-2">ID</th>
            <th className="text-left p-2">Nama</th>
            <th className="text-left p-2">Email</th>
            <th className="text-left p-2">Level</th>
          </tr>
        </thead>
        <tbody>
          {users.map((user) => (
            <tr key={user.id.toString()} className="border-b">
              <td className="p-2">{user.id.toString()}</td>
              <td className="p-2">{user.name}</td>
              <td className="p-2">{user.email}</td>
              <td className="p-2">{user.level ?? '-'}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  )
}

⚠️ Catatan tipe data: kolom id bertipe BigInt di MySQL/Prisma. React/Next.js tidak bisa merender BigInt langsung di JSX — wajib .toString(), kalau tidak akan muncul error Do not know how to serialize a BigInt.

Contoh Route dengan Relasi — /roles

Model role berelasi ke rolecat dan appmenunew, jadi gunakan include:

mkdir -p app/roles
// app/roles/page.tsx
import { prisma } from '@/lib/prisma'

export default async function RolesPage() {
  const roles = await prisma.role.findMany({
    include: {
      rolecat: true,
      appmenunew: true,
    },
    orderBy: { idrole: 'desc' },
  })

  if (roles.length === 0) {
    return <p className="p-6 text-gray-500">Belum ada data role.</p>
  }

  return (
    <div className="p-6">
      <h1 className="text-2xl font-bold mb-4">Daftar Role</h1>
      <table className="w-full border-collapse">
        <thead>
          <tr className="border-b">
            <th className="text-left p-2">ID</th>
            <th className="text-left p-2">Kategori Role</th>
            <th className="text-left p-2">Menu</th>
          </tr>
        </thead>
        <tbody>
          {roles.map((role) => (
            <tr key={role.idrole} className="border-b">
              <td className="p-2">{role.idrole}</td>
              <td className="p-2">{role.rolecat?.rolecat ?? '-'}</td>
              <td className="p-2">{role.appmenunew?.appmenunew ?? '-'}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  )
}

Field idrole bertipe Int, jadi tidak perlu .toString() seperti id di tabel users.


10. Jalankan Project

npm run dev

Akses di browser:

http://localhost:3000/users
http://localhost:3000/roles

Akses dari IP Lain / VPS (bukan localhost) — kalau muncul warning allowedDevOrigins, tambahkan di next.config.js:

module.exports = {
  allowedDevOrigins: ['192.168.1.100'],
}

Lalu restart dev server.


Troubleshooting — Ringkasan Masalah yang Pernah Terjadi

Error Penyebab Solusi
P1013: database string invalid, must start with postgresql:// Schema masih default Postgres, padahal DB-nya MySQL Set provider = "mysql", samakan format DATABASE_URL
Cannot find module '@/lib/prisma' File belum dibuat, atau alias @/ tidak sesuai struktur folder Cek paths di tsconfig.json
Module '@prisma/client' has no exported member 'PrismaClient' Client di-generate ke folder custom (output), bukan ke @prisma/client Import dari path output yang sebenarnya
Expected 1 arguments, but got 0 saat new PrismaClient() Prisma v7 menghapus query engine internal, wajib driver adapter Pakai Prisma v6 tanpa adapter
@prisma/client did not initialize yet Belum pernah prisma generate, atau import path salah Jalankan npx prisma generate, sesuaikan import
GET /roles 404 File tersimpan sebagai app/roles.tsx, bukan app/roles/page.tsx mkdir -p app/roles && mv app/roles.tsx app/roles/page.tsx
Do not know how to serialize a BigInt Kolom id bertipe BigInt, tidak bisa langsung di-render JSX Tambahkan .toString()
Hydration mismatch warning di browser Biasanya dari browser extension, bukan dari kode Bisa diabaikan, cek di mode incognito

Checklist Cepat Kalau Ada Masalah

  1. npx prisma -v → pastikan versi sesuai (v6 untuk tutorial ini)
  2. cat prisma/schema.prisma → cek provider dan baris output
  3. npx prisma generate → perhatikan baris "Generated Prisma Client ... to ..."
  4. Import PrismaClient di lib/prisma.ts harus mengarah ke lokasi hasil generate yang sebenarnya
  5. Struktur route harus app/nama-route/page.tsx, bukan file lepas
  6. Restart dev server setiap habis mengubah tsconfig.json, next.config.js, atau schema Prisma
  7. Kolom bertipe BigInt wajib .toString() sebelum dirender di JSX